//
// @Author: Zhenwei Xiao
// @Description:
// @Version: 1.0.0
// @Date: 2021/2/28 5:16 下午
// @Copyright: MIN-Group；国家重大科技基础设施——未来网络北大实验室；深圳市信息论与未来网络重点实验室
//


const sec = require("../../crypto");
const com = require("../../common");
const sm2 = require('../sm2/external');
const sm4 = require("sm-crypto").sm4;
const ver = require("./verify")
const uti = require("../utils");

const CertVersion = {
	VERSION1:  1
}

class tbsCertificate  {
    constructor(Version,SerialNumber,PublicKey,SignatureAlgorithm,PublicKeyAlgorithm,IssueTo,Issuer,NotBefore,NotAfter,KeyUsage,IsCA,Timestamp)
    {
        this.Version = Version;
        this.SerialNumber = SerialNumber;
        this.PublicKey = PublicKey;
        this.SignatureAlgorithm = SignatureAlgorithm;
        this.PublicKeyAlgorithm = PublicKeyAlgorithm;
        this.IssueTo = IssueTo;
        this.Issuer = Issuer;
        this.NotBefore = NotBefore;
        this.NotAfter = NotAfter;
        this.KeyUsage = KeyUsage;
        this.IsCA = IsCA;
        this.Timestamp = Timestamp;
        
    }
	// Version             int
	// SerialNumber        int64
	// PublicKey           []byte //公钥
	// SignatureAlgorithm  sec.SignatureAlgorithm
	// PublicKeyAlgorithm  sec.PublicKeyAlgorithm //公钥算法
	// IssueTo             string                 //被签发者
	// Issuer              string                 //签发者
	// NotBefore, NotAfter int64                  //有效期
	// KeyUsage            sec.KeyUsage           //用途
	// IsCA                bool                   //是否自签
	// Timestamp           int64                  //时间戳

}

class certificate extends tbsCertificate {
    constructor(TBSCertificate,SignatureAlgorithm,SignatureValue){
        //super(TBSCertificate.Version,TBSCertificate.SerialNumber,TBSCertificate.PublicKey,TBSCertificate.SignatureAlgorithm,TBSCertificate.PublicKeyAlgorithm,TBSCertificate.IssueTo,TBSCertificate.Issuer,TBSCertificate.NotBefore,TBSCertificate.NotAfter,TBSCertificate.KeyUsage,TBSCertificate.IsCA,TBSCertificate.Timestamp);
        super(TBSCertificate)
		this.SignatureAlgorithm = SignatureAlgorithm;
        this.SignatureValue = SignatureValue
    }

	// TBSCertificate     tbsCertificate
	// SignatureAlgorithm sec.SignatureAlgorithm
	// SignatureValue     []byte
}

class Certificate {
    constructor(Version,SerialNumber,PublicKey,Signature,SignatureAlgorithm,PublicKeyAlgorithm,IssueTo,Issuer,NotBefore,NotAfter,KeyUsage,IsCA,Timestamp)
    {
        this.Version = Version;
        this.SerialNumber = SerialNumber;
        this.PublicKey = PublicKey;
        this.Signature = Signature;
        this.SignatureAlgorithm = SignatureAlgorithm;
        this.PublicKeyAlgorithm = PublicKeyAlgorithm;
        this.IssueTo = IssueTo;
        this.Issuer = Issuer;
        this.NotBefore = NotBefore;
        this.NotAfter = NotAfter;
        this.KeyUsage = KeyUsage;
        this.IsCA = IsCA;
        this.Timestamp = Timestamp;
        
    }
	// Version             int                    //版本号
	// SerialNumber        int64	               //序列号
	// PublicKey           sec.PublicKey            //公钥
	// Signature           []byte                 //签名
	// SignatureAlgorithm  sec.SignatureAlgorithm //签名算法
	// PublicKeyAlgorithm  sec.PublicKeyAlgorithm //公钥算法
	// IssueTo             string                 //被签发者
	// Issuer              string                 //签发者
	// NotBefore, NotAfter int64                  //有效期
	// KeyUsage            sec.KeyUsage           //用途
	// IsCA                bool                   //是否自签
	// Timestamp           int64                  //时间戳

	//
	// 使用私钥priv对证书cert进行签名
	//
	// @Description:
	// @receiver cert
	// @param priv sec.PrivateKey
	// @return error
	//
    SignCert(priv) {
		var b
		try{
			let innercert = parseCertToInnerCert(this)
			b = JSON.stringify(innercert.TBSCertificate)
			console.log("内部证书json转化结果", b)
		}catch(err){
            return err
		}
	    switch (this.SignatureAlgorithm) {
	    case com.SignatureAlgorithm.SM2WithSM3:
		    let hash = new sec.Hash(sec.HashType.SM3)
		    let hashFunc = hash.New()
		    let digest = hashFunc(b)
		    let sig = priv.Sign(digest,"")
		    if (sig[1] != null) {
		    	return sig[1]
		    }
		    this.Signature = sig[0]
		    return null
	    default:
	 	    return new Error("Unknown SignatureAlgorithm")
	    }
    }

	//
	// 证书结构体转换为pem
	//
	// @Description:
	// @receiver cert
	// @param passwd []byte
	// @param mode  com.SymmetricAlgorithm
	// @return string
	// @return error
	//
    ToPem(passwd, mode) {
	    if (this == null || this.Signature == null) {
		    return ["", new Error("Wrong certificate")]
	    }
		var b
		try{		
			b = JSON.stringify(parseCertToInnerCert(this))
			// if err != nil {
			//     return "", errors.New("asn1 marshal fails")
			// }
			console.log("to pem json序列化结果", b)
		}catch(err){
			return ["", new Error("Json marshal fails")]
		}
	    var ret
	    if (passwd != null && uti.Bytes2Str(passwd).length > 0) {
			let hash = new sec.Hash(sec.HashType.SM3)
		    let hashFunc = hash.New()
		    let passHash = hashFunc(uti.Bytes2Str(passwd))
			let passHashBytes = uti.Str2Bytes(passHash)
			console.log("passHashBytes is", passHashBytes)
		    if (passHashBytes.length == 64) {
			    for (var i = 0; i < 32; i++) {
				    passHashBytes[i] = passHashBytes[i+32] + passHashBytes[i]
			    }
		    }
			console.log("passHash is ", passHash)
            console.log("passHashBytes is ", passHashBytes)
			console.log("passHashBytes.length is ", passHashBytes.length)
		    switch (mode) {
		    case com.SymmetricAlgorithm.SM4ECB:
				try{
					console.log("passHashStr is", uti.Bytes2Str(passHashBytes).substring(0,31))
					ret = sm4.encrypt(b, uti.Bytes2Str(passHashBytes).substring(0,31))
					ret = new Buffer.from(uti.Bytes2Str(ret),'hex').toString('base64')
				}catch(err){
					return ["",err.message]
				}
				break;
		    case com.SymmetricAlgorithm.SM4CBC:
				// TO DO
			    // ret, err = sm4.Sm4Cbc(passHash[:16], b, sm4.ENC)
			    // if err != nil {
				//     return "", err
			    // }
		    default:
			    return ["", new Error("Unknown SymmetricAlgorithm")]
				
		    }
	    } else {
			console.log("passwd为空")
		    ret = new Buffer.from(uti.Bytes2Str(b)).toString('base64')
	    }
		console.log("ret", ret)
	    return [ret, null]
    }
    //
	// pem转换为证书结构体
	//
	// @Description:
	// @receiver cert *Certificate
	// @param pemStr string
	// @param passwd []byte
	// @param mode com.SymmetricAlgorithm
	// @return error
	//
	FromPem(pemStr, passwd, mode) {
		if (pemStr === null||pemStr === ""||pemStr === undefined) {
			return new Error("Wrong certificate pem")
		}
		var pemByte
		var decCert 
		if (passwd != null && uti.Bytes2Str(passwd).length > 0) {
			let hash = new sec.Hash(sec.HashType.SM3)
		    let hashFunc = hash.New()
		    let passHash = hashFunc(uti.Bytes2Str(passwd))
			let passHashBytes = uti.Str2Bytes(passHash)
			if (passHashBytes.length == 64) {
			    for (var i = 0; i < 32; i++) {
				    passHashBytes[i] = passHashBytes[i+32] + passHashBytes[i]
			    }
		    }
			switch (mode) {
			case com.SymmetricAlgorithm.SM4ECB:
				try{
					// console.log(new Buffer.from(pemStr, 'base64').toString("hex"))
					pemByte = uti.Str2Bytes(new Buffer.from(pemStr, 'base64').toString("hex"))
				}catch(err){
					return err
				}
				console.log("The pemByte is", pemByte)
				try{
					console.log("passHashStr is", uti.Bytes2Str(passHashBytes).substring(0,31))
					decCert = sm4.decrypt(uti.Bytes2Str(pemByte), uti.Bytes2Str(passHashBytes).substring(0,31));
				}catch(err){
					return err.message
				}
				break;
			case com.SymmetricAlgorithm.SM4CBC:
				// TO DO
				// decCert, err = sm4.Sm4Cbc(passHash[:16], pemByte, sm4.DEC)
				// if err != nil {
				// 	return err
				// }
			default:
				return new Error("Unknown SymmetricAlgorithm")
			}
		} else {
			console.log("passwd为空")
			decCert = pemStr
		}

		let c = new certificate
		console.log("decCert is", decCert)
		try{
			c = JSON.parse(decCert)
			console.log("json解析后：", c)
			console.log("json解析后PublicKey情况", c.TBSCertificate.PublicKey)
			//_, err = asn1.Unmarshal(decCert, c)
			// if err != nil {
			// 	return errors.New("wrong password")
			// }
			console.log("cert.TBSCertificate.Version", c.TBSCertificate.Version)
		}catch(err){
			return err.message
		}
		let err = parseInnerCertToCert(c, this)
		if (err != null) {
			return err
		}
		return null
	}
}

//
// 证书结构体转换为内部证书结构体
//
// @Description:
// @param cert *Certificate
// @return certificate
//
function parseCertToInnerCert(cert){
	tbsCert = new tbsCertificate
	tbsCert.Version = cert.Version
	tbsCert.SerialNumber = cert.SerialNumber
	tbsCert.PublicKey = cert.PublicKey.GetBytes()
	tbsCert.SignatureAlgorithm = cert.SignatureAlgorithm
	tbsCert.PublicKeyAlgorithm = cert.PublicKeyAlgorithm
	tbsCert.IssueTo = cert.IssueTo
	tbsCert.Issuer = cert.Issuer
	tbsCert.NotBefore = cert.NotBefore
	tbsCert.NotAfter = cert.NotAfter
	tbsCert.KeyUsage = cert.KeyUsage
	tbsCert.IsCA = cert.IsCA
	tbsCert.Timestamp = cert.Timestamp

	certInner = new certificate
	certInner.SignatureAlgorithm = cert.SignatureAlgorithm
	certInner.SignatureValue = cert.Signature
	certInner.TBSCertificate = tbsCert

	return certInner
}

//
// 内部证书结构体转换为证书结构体
//
// @Description:
// @param cert *certificate
// @param c *Certificate
// @return error
//
function parseInnerCertToCert(cert , c){
	c.Version = cert.TBSCertificate.Version
	c.SerialNumber = cert.TBSCertificate.SerialNumber
    //  TO DO 错误处理
	let publicKey = unMarshalPublicKey(cert.TBSCertificate.PublicKey, cert.TBSCertificate.PublicKeyAlgorithm)
	// if err != 0{
	// 	return err
	// }
	c.PublicKey = publicKey
	c.Signature = cert.SignatureValue
	c.SignatureAlgorithm = cert.TBSCertificate.SignatureAlgorithm
	c.PublicKeyAlgorithm = cert.TBSCertificate.PublicKeyAlgorithm
	c.IssueTo = cert.TBSCertificate.IssueTo
	c.Issuer = cert.TBSCertificate.Issuer
	c.NotBefore = cert.TBSCertificate.NotBefore
	c.NotAfter = cert.TBSCertificate.NotAfter
	c.KeyUsage = cert.TBSCertificate.KeyUsage
	c.IsCA = cert.TBSCertificate.IsCA
	c.Timestamp = cert.TBSCertificate.Timestamp

	return null
}

//
// 公钥解码
//
// @Description:
// @param pubByte []byte
// @param algo com.PublicKeyAlgorithm
// @return PublicKey sec.PublicKey 
// @return error error
//
function unMarshalPublicKey(pubByte, algo) {
	if (pubByte.length === 0) {
		return null
	}
	switch (algo) {
	case com.PublicKeyAlgorithm.SM2:
		let p = new sm2.Sm2PublicKey
        p.SetBytes(pubByte)
		let res = new sec.PublicKey(p.PublicKey)
		// p = sm2.Sm2PublicKey{}
		// err := p.SetBytes(pubByte)
		// if err != nil{return nil, err}
		// var res sec.PublicKey = &p
		return res
	}
	return null
}

//
// 验证证书合法性，包括任何层面的检查
//
// @Description:
// @param cert Certificat
// @param ca Certificat
// @return bool
// @return error
//
function Verify(cert, ca) {
	var myInnercert = parseCertToInnerCert(cert)
	switch (myInnercert.TBSCertificate.Version) {
	case CertVersion.VERSION1:
		err = CheckDuration(myInnercert)
		if (err != null) {
			return [false, err]
		}
		if (cert.IsCA) {
			return CheckSign(myInnercert, cert.PublicKey)
		} else {
			return CheckSign(myInnercert, ca.PublicKey)
		}
	default:
		var Reason = ver.InvalidReason.IncompatibleVersion
		var Detail = ""
		CertificateInvalidError = new CertificateInvalidError(Reason,Detail)
		return [false, CertificateInvalidError]
	}
}

//
// 证书有效期检查
//
// @Description:
// @param cert certificate
// @return error
//
function CheckDuration (cert)  {
	currTime = parseInt(new Date().getTime()/1000)
	if (cert.TBSCertificate.NotAfter > currTime) {
		var Reason = ver.InvalidReason.NotReachEffectiveDate
		var unixTimestamp = new Date( cert.TBSCertificate.NotAfter*1000 ) ;
        commonTime = unixTimestamp.toLocaleString();
		var Detail = "the effective date is " + commonTime
		let CertificateInvalidError = new CertificateInvalidError(Reason, Detail)
		return CertificateInvalidError
	}
	if (currTime > cert.TBSCertificate.NotBefore) {
		var Reason = ver.InvalidReason.Expired
		var unixTimestamp = new Date( cert.TBSCertificate.NotBefore*1000 ) ;
        commonTime = unixTimestamp.toLocaleString();
		var Detail = "the expiration date is " + commonTime,	
		CertificateInvalidError = new CertificateInvalidError(Reason, Detail)
		return CertificateInvalidError		
	}
	return null
}

//
// 证书签名检查
//
// @Description:
// @param cert certificate
// @param pub com.PublicKey
// @return bool
// @return error
//
function CheckSign(cert, pub) {
	switch (cert.TBSCertificate.SignatureAlgorithm) {
	case com.SignatureAlgorithm.SM2WithSM3:
		// let asn1 = asn1js.fromBER(cert.TBSCertificate); 
		// let b = asn1.result
		var b
		try{
			b = JSON.stringify(cert.TBSCertificate)
		}catch(err){
			var Reason = ver.InvalidReason.MarshalError
			var Detail = err.message
			var CertificateInvalidError = new ver.CertificateInvalidError(Reason,Detail)
			return [false, CertificateInvalidError]
		}
		let hash = new sec.Hash(sec.HashType.SM3)
		let hashFunc = hash.New()
		let digest = hashFunc(b)
		let ret = pub.Verify(digest,cert.SignatureValue,"")
		if (ret[1] != null) {
			var Reason = ver.InvalidReason.SignatureError
			var Detail = ret[1]
			CertificateInvalidError = new ver.CertificateInvalidError(Reason,Detail)
			return [false, CertificateInvalidError]
		}
		if (!ret[0]) {
			var Reason = ver.InvalidReason.CANotAuthorizedForThisName
			var Detail = ""
			let CertificateInvalidError = new ver.CertificateInvalidError(Reason,Detail)
			return [false, CertificateInvalidError]
		}
		return [true, null]
	default:
		var Reason = ver.InvalidReason.UnknownSignatureAlgorithm
		var Detail = cert.TBSCertificate.SignatureAlgorithm.toString()
		var CertificateInvalidError = new ver.CertificateInvalidError(Reason,Detail)
		return [false, CertificateInvalidError]
	}
}

Date.prototype.toLocaleString = function() {
	return this.getFullYear() + "-" + (this.getMonth() + 1) + "-" + this.getDate() + " " + this.getHours() + ":" + this.getMinutes() + ":" + this.getSeconds();
};

module.exports = {
    CertVersion: CertVersion,
    certificate: certificate,
	tbsCertificate: tbsCertificate,
	Certificate: Certificate,
	parseCertToInnerCert: parseCertToInnerCert,
	parseInnerCertToCert: parseInnerCertToCert,
	unMarshalPublicKey: unMarshalPublicKey,
	Verify: Verify,
	CheckDuration: CheckDuration,
    CheckSign: CheckSign
}